import collections
import copy
import os
import re
import xmltodict

from Extend.FileMethod import FileExtend
from Extend.SqlHelper import SqlServer
from sanweijiaXml.MprMethods import Algorithm
from sanweijiaXml.model import *


class DataConvert:
    def __init__(self, server, user, password, db):
        self.sql_method = GetSQLData(server, user, password, db)
        self.ls_method = SqlServer('172.16.200.5', 'sa', 'Liweijia456', 'EasyFAS_Lwj_Release')

    def getOrderInfo(self, order):
        data_mode = DataModel()
        my_cabinet = self.setOrderInfo(order)
        data_mode.cabinet.append(my_cabinet)
        path = r'\\172.16.200.252\product\CNC\{}'.format(order)
        panels = self.sql_method.getPanelInfo(order)
        order_edge = self.sql_method.getOrdersEdge(order)
        my_cabinet.panels += self.edgeToPanel(panels, order_edge, path)
        my_cabinet.metals += self.addMateriel(order)
        return data_mode.myResult

    def edgeToPanel(self, panels, order_edges, path):
        panel_models = []
        for panel in panels:
            panel_model = DataToModel.PanelToDict(panel)
            edges = []
            MprConvert.changePanelInfo(panel_model, panel, path)
            for order_edge in order_edges:
                if panel['ImosBoardId'] == order_edge['ImosBoardId']:
                    edges.append(DataToModel.EdgeToDict(order_edge))
            panel_model.EdgeGroup.edges = edges
            # 减去封边
            self.minusTheEdgeBandingSize(panel_model)
            # 旋转到合适的纹理
            self.rotateTexture(panel_model)
            panel_models.append(panel_model)
        return panel_models

    def addMateriel(self, order):
        materiel_list = self.sql_method.getMateriel(order)
        new_materiel = []
        for materiel in materiel_list:
            new_materiel.append(DataToModel.MaterielToDict(materiel))
        return new_materiel

    def setOrderInfo(self, order):
        my_cabinet = Cabinet()
        order_info = self.getOrderMessage(order)
        my_cabinet.OrderNo = order
        try:
            my_cabinet.Name = order_info['ProductName']
            my_cabinet.Customer = order_info['ClientName']
            my_cabinet.Designer = order_info['Designer']
        except:
            pass
        return my_cabinet

    def getOrderMessage(self, order_no):
        sql = "select ClientName,ProductName,Designer from C_Order where OrderNo = '{}'".format(order_no)
        return self.ls_method.getOneData(sql)

    def readOneFileOrDir(self, path):
        if os.path.isdir(path):
            files = [os.path.join(path, file) for file in os.listdir(path) if os.path.isfile(os.path.join(path, file))]
        else:
            files = [path]
        data_mode = DataModel()
        my_cabinet = Cabinet()
        my_cabinet.Name = '文件转换'
        data_mode.cabinet.append(my_cabinet)
        for file in files:
            file_name = os.path.basename(file)
            file_name = re.sub(r'\..*', '', file_name)
            match_info = MprConvert.readMpr(os.path.dirname(file), file_name)
            panel = {'BarCode': file_name, 'Name': file_name, 'Color': "", 'INFO1': "",
                     'MATGROR': 0, 'Length': match_info[0][0]['_BSX'], 'Width': match_info[0][0]['_BSY'],
                     'Height': match_info[0][0]['_BSZ'], 'CutLength': "", "CutWidth": ""}
            panel_model = DataToModel.PanelToDict(panel)
            MprConvert.changePanelInfo(panel_model, panel, os.path.dirname(file))
            wire = panel_model.machine[len(panel_model.machine) - 1]
            if len(wire):
                self.createEdge(panel_model, len(wire))
            else:
                self.createEdge(panel_model, 4)
            my_cabinet.panels.append(panel_model)
        return data_mode.myResult

    @staticmethod
    def createEdge(panel_model, edge_count):
        for x in range(edge_count):
            edge = Edge()
            edge.LineID = x
            edge.Face = x
            panel_model.EdgeGroup.edges.append(edge)

    @staticmethod
    def minusTheEdgeBandingSize(panel_model: Panel):
        # 有外部框线，异形板件
        if 3 in [match.wire_type for match in panel_model.machine]:
            RemoveEdgeBanding.hasOut(panel_model)
        # 无外部框线，常规板件，常规板件四个边，从零开始
        else:
            RemoveEdgeBanding.notOut(panel_model)

    @staticmethod
    def rotateTexture(panel_model: Panel):
        if panel_model.Grain == 'V':
            panel_model.Length, panel_model.Width = panel_model.Width, panel_model.Length
            panel_model.ActualLength, panel_model.ActualWidth = panel_model.ActualWidth, panel_model.ActualLength
            for matching in panel_model.machine:
                matching.X, matching.Y = matching.Y, matching.X
                for line in matching.lines:
                    line.EndX, line.EndY = line.EndY, line.EndX


class RemoveEdgeBanding:

    @classmethod
    # 有外部框线
    def hasOut(cls, panel_model: Panel):
        # 因为有外部框线，因此我们需要拿出外部框线
        line_parent = Machining()
        for match in panel_model.machine:
            if match.wire_type == 3:
                line_parent = match
        # 因为是外部的框线，因此起点和终点是重合的
        min_point = min(line_parent.lines, key=lambda x: (x.EndY, x.EndX))
        min_point_index = int(min_point.LineID) - 1
        # 判断线条的走向
        forward_rotation = cls.getLineTrend(line_parent, min_point_index)
        # 更改外部线框
        edges = cls.edgeSealingPosition(line_parent, panel_model.EdgeGroup.edges, forward_rotation, min_point_index)
        # 改变板件开料尺寸
        panel_model.ActualLength -= edges[1] + edges[3]
        panel_model.ActualWidth -= edges[0] + edges[2]
        # 改变线框起始点位置
        last_line = line_parent.lines[len(line_parent.lines) - 1]
        line_parent.X, line_parent.Y = last_line.EndX, last_line.EndY
        # 改变所有线框槽的位置
        for machining in panel_model.machine:
            if machining.wire_type != 3:
                cls.changeInternal(machining, panel_model, edges)
        # print(min_point)

    @classmethod
    def getLoopIndex(cls, end_index, start_index, reverse=False):
        if reverse:
            x = list(range(start_index, end_index)) + list(range(0, start_index))
        else:
            x = [abs(i) for i in (range(-start_index, 1))] + list(range(end_index + 1, start_index, -1))
        return x

    @classmethod
    def edgeSealingPosition(cls, machining: Machining, edges: list, forward_rotation: bool, min_index: int):
        # 获得极限坐标
        min_y = min(machining.lines, key=lambda x: x.EndY).EndY
        max_y = max(machining.lines, key=lambda x: x.EndY).EndY
        min_x = min(machining.lines, key=lambda x: x.EndX).EndX
        max_x = max(machining.lines, key=lambda x: x.EndX).EndX
        edge_up, edge_down, edge_left, edge_right = int, int, int, int
        # if forward_rotation:
        edge_loop = 0
        # 获取上一次的下标，因为这里排序不确定，因此怕下标错误，因此记录一次
        private_index = 0
        # 上一次减封边的坐标，1为x 2为y 0为未减，主要是防止，同一个坐标被减两次导致框线变形
        private_axis = 0
        line_index = cls.getLoopIndex(len(machining.lines), min_index, forward_rotation)
        for index in line_index:
            if edge_loop < len(edges):
                machining.lines[index].edge = edges[edge_loop]
            if machining.lines[index].EndY == max_y and edge_up is int:
                edge_up = edges[edge_loop]
            elif edge_loop == 0 and edge_down is int:
                edge_down = edges[edge_loop]
            elif machining.lines[index].EndX == min_x and machining.lines[index].EndY != min_y and edge_left is int:
                edge_left = edges[edge_loop - 1]
            elif machining.lines[index].EndX == max_x and edge_right is int:
                edge_right = edges[edge_loop]
            if edge_loop > 0:
                if cls.getWireTrend(machining.lines[private_index], machining.lines[index]):
                    if private_axis != 2:
                        machining.lines[private_index].EndY -= machining.lines[private_index].edge.Thickness
                    machining.lines[index].EndY -= machining.lines[private_index].edge.Thickness
                    private_axis = 2
                else:
                    if private_axis != 1:
                        machining.lines[private_index].EndX -= machining.lines[private_index].edge.Thickness
                    machining.lines[index].EndX -= machining.lines[private_index].edge.Thickness
                    private_axis = 1
            private_index = index
            edge_loop += 1
        # 执行完上面的操作只是让边减去了自己应该减去的值但是没有减去最左边和最下面的边，因此还需要再减一次，但是这里又不能让已经减过的值再去减一次
        private_index = 0
        for index in line_index:
            if index == line_index[0]:
                private_index = index
                continue
            if machining.lines[index].edge is edge_left or machining.lines[private_index].edge is edge_left:
                machining.lines[index].EndY -= edge_down.Thickness
            elif machining.lines[index].edge is edge_down or machining.lines[private_index].edge is edge_down:
                machining.lines[index].EndX -= edge_left.Thickness
            else:
                machining.lines[index].EndX -= edge_left.Thickness
                machining.lines[index].EndY -= edge_down.Thickness
            private_index = index
        return [edge_down.Thickness, edge_right.Thickness, edge_up.Thickness, edge_left.Thickness]

    @classmethod
    def getWireTrend(cls, start_point: Line, end_point: Line):
        if start_point.EndY == end_point.EndY:
            return True
        else:
            return False

    @classmethod
    def getLineTrend(cls, line_parent, index: int):
        line_count = len(line_parent.lines)
        next_index = index + 1 if index + 1 < line_count else line_count - index + 1
        if line_parent.lines[index].EndX < line_parent.lines[next_index].EndX:
            return True
        elif line_parent.lines[index].EndX > line_parent.lines[next_index].EndX:
            return False
        else:
            cls.getLineTrend(line_parent, next_index)

    # 无外部框线
    @classmethod
    def notOut(cls, panel_model):
        # 因为无外部框线，因此直接减去封边尺寸
        edges = [face.Thickness for face in panel_model.EdgeGroup.edges]
        panel_model.ActualWidth -= (edges[0] + edges[2])
        panel_model.ActualLength -= (edges[1] + edges[3])
        # 修改内部的孔位、槽和框线
        for match in panel_model.machine:
            cls.changeInternal(match, panel_model, edges)

    @classmethod
    def changeInternal(cls, match, panel_model, edges):
        if match.Face in ['1', '2', '3', '4']:
            return
        old_x, old_y = match.X, match.Y
        match.X -= edges[3]
        match.Y -= edges[0]
        # 如果是槽
        if match.wire_type == 1:
            for line in match.lines:
                # x相同则为竖向的
                if line.EndX == old_x:
                    # 从上往下
                    if line.EndY < old_y:
                        line.EndY -= edges[0]
                        match.Y -= edges[2]
                        if match.Y > panel_model.ActualWidth:
                            match.Y = panel_model.ActualWidth
                    else:
                        line.EndY -= (edges[2] + edges[0])
                        if line.EndY > panel_model.ActualWidth:
                            line.EndY = panel_model.ActualWidth
                    line.EndX = line.EndX - edges[3]
                # y相同则为横向的
                elif line.EndY == old_y:
                    # 从右往左
                    if line.EndX < old_x:
                        line.EndX -= edges[3]
                        match.X -= edges[1]
                        if match.X > panel_model.ActualLength:
                            match.X = panel_model.ActualLength
                    else:
                        line.EndX -= (edges[3] + edges[1])
                        if line.EndX > panel_model.ActualLength:
                            line.EndX = panel_model.ActualLength
                    line.EndY -= edges[0]
                else:
                    line.EndX -= edges[3]
                    line.EndY -= edges[0]
        # 如果是线
        elif match.wire_type == 2:
            for line in match.lines:
                line.EndX -= edges[3]
                line.EndY -= edges[0]


class MprConvert:

    @classmethod
    def changePanelInfo(cls, panel_model: Panel, panel: dict, path: str):
        # panel_model.machine += MprConvert.readMpr(path, panel['BarCode'])
        match_info = MprConvert.readMpr(path, panel['BarCode'])
        if match_info:
            if float(panel_model.Length) < float(match_info[0][0]['_BSX']) and float(panel_model.Width) > float(
                    match_info[0][0]['_BSY']):
                panel_model.Length, panel_model.Width = panel_model.Width, panel_model.Length
            panel_model.ActualLength = float(match_info[0][0]['_BSX']) + float(match_info[0][0]['AX_EXP'])
            panel_model.ActualWidth = float(match_info[0][0]['_BSY']) + float(match_info[0][0]['AY_EXP'])
            panel_model.machine += cls.getMatchingModel(match_info)

    '''此方法会获取到正面条码和反面条码，为一个列表，第一个列表正面，第二个为反面，然后每个列表当中包含一组变量和一组孔位和板件信息'''

    @classmethod
    def readMpr(cls, path: str, barcode: str):
        positive_file = os.path.join(path, barcode + '.mpr')
        back_code = re.sub(r'(.*?)(\d)(\d{3})$', lambda x: x.group(1) + str(2) + x.group(3), barcode)
        back_file = os.path.join(path, back_code + '.mpr')
        mrp_info = []
        file_list = [positive_file]
        if positive_file != back_file:
            file_list.append(back_file)
        try:
            for file in file_list:
                mrp_info.append(MprConvert.readMprFile(file))
        except Exception as ee:
            print(ee)
            pass
        return mrp_info

    @classmethod
    def readMprFile(cls, file_path):
        mpr_content = FileExtend.readFile(file_path)
        mpr = re.sub('\n', '\t', mpr_content)
        all_hole = re.search('(<.*)!', mpr).group()
        all_hole = [x for x in all_hole.split('<') if x]
        holes = [DataToModel.shortContentToDict(x) for x in all_hole]
        # hole_var = {}
        hole_var = collections.OrderedDict()
        for x in range(len(holes)):
            if holes[x]['stat'] == '100':
                hole_var = holes.pop(x)
                cls.getVarnish(hole_var, mpr_content)
                break
        return [hole_var, holes]

    # 用于mpr文件定义变量
    @classmethod
    def getVarnish(cls, hole_var, mpr_content):
        # 基础尺寸变量
        for element in list(hole_var.keys()):
            if not hole_var[element].isnumeric():
                re_result = re.search(r'{}=.*'.format(hole_var[element]), mpr_content).group()
                ele = re_result.split('=')
                hole_var[ele[0]] = ele[1].replace('"', '').replace("'", '')
                hole_var[element] = ele[1].replace('"', '').replace("'", '')
        # 板件尺寸
        for elem in ['_BSX', '_BSY', '_BSZ']:
            re_result = re.search(r'{}=.*'.format(elem), mpr_content).group()
            ele = re_result.split('=')
            hole_var[elem] = ele[1].replace('"', '').replace("'", '')
        # 线条圆角定义
        extend = cls.getLineAndFillet(mpr_content)
        hole_var['MyExtend'] = extend

    @classmethod
    def getLineAndFillet(cls, mpr_content: str):
        mpr = re.sub('\n', '\t', mpr_content)
        result = re.search(r'\](\d+.*)\[\d+', mpr).group(1)
        lines = result.split(']')
        # extend_var = {}
        extend_var = collections.OrderedDict()
        for line in lines:
            key = re.search(r'^\d+', line).group()
            # child_vars = {}
            child_vars = collections.OrderedDict()
            for line_child_vars in line.split('$'):
                if len(line_child_vars) < 5:
                    continue
                child = re.search(r'\d+', line_child_vars).group()
                loop = 0
                # var = {}
                var = collections.OrderedDict()
                for child_var in re.split(r'\s', line_child_vars):
                    if loop == 0:
                        pass
                    elif loop == 1:
                        var['WorkType'] = child_var
                    else:
                        if child_var.count('=') == 1:
                            ele = re.search('(.*)?=(.*)', child_var)
                            var[ele.group(1)] = ele.group(2)
                    loop += 1
                child_vars[child] = var
            extend_var[key] = child_vars
        return extend_var

    @classmethod
    def getMatchingModel(cls, hole_values: list):
        element = []
        loop = 0
        for holes_list in hole_values:
            hole_var = holes_list[0]
            for hole in holes_list[1]:
                if hole is None:
                    continue
                match = DataToModel.MPRInfoToMachining(hole, hole_var, True if loop > 0 else False)
                if match is not None:
                    element.append(match)
                else:
                    print(hole)
            loop += 1
        else:
            hole_var = hole_values[0][0]
            # 增加线框  正面文件的变量当中的扩展信息，也就是板件的线条
            out_line = Algorithm.addOutline(hole_var['MyExtend'])
            if out_line:
                out_line.Depth = float(hole_var['_BSZ']) + 0.1
                element.append(out_line)
        return element


class GetSQLData:
    def __init__(self, server, user, password, db):
        self.server = server
        self.db = SqlServer(self.server, user, password, db)

    def getNeedData(self, order_no):
        panels = self.getPanelInfo(order_no)
        materiel = self.getMateriel(order_no)
        order_edge = self.getOrdersEdge(order_no)
        return panels, materiel, order_edge

    def getPanelInfo(self, order_no):
        return self.db.getProc('searchOrderBoard', order_no)

    def getMateriel(self, order_no):
        return self.db.getProc('searchOrderMateriel', order_no)

    def getOrdersEdge(self, order_no):
        return self.db.getProc('searchOrderEdge', order_no)


class DataToModel:

    @classmethod
    def PanelToDict(cls, panel: dict):
        panel_model = Panel()
        panel_model.ID = panel['BarCode']
        # panel_model.ID = panel['ImosBoardId']
        panel_model.Name = panel['Name']
        panel_model.Material = panel['Color']
        panel_model.BasicMaterial = panel['MaterialTypeInfo']
        panel_model.edgeMaterial = panel['Color']
        if panel['MATGROR'] == 90:
            panel_model.Grain = 'H'
        #     panel_model.Length = panel['Width']
        #     panel_model.Width = panel['Length']
        # else:
        #     panel_model.Length = panel['Length']
        #     panel_model.Width = panel['Width']
        panel_model.Length = panel['Length']
        panel_model.Width = panel['Width']
        # panel_model.ActualLength = panel['CutLength']
        # panel_model.ActualWidth = panel['CutWidth']
        panel_model.Thickness = panel['Height']
        return panel_model

    @staticmethod
    def EdgeToDict(edge: dict):
        edge_model = Edge()
        edge_model.Thickness = edge['Thick']
        edge_model.Length = edge['Length']
        edge_model.LindID = edge['EdgeSeq']
        edge_model.Face = edge['EdgeSeq']
        return edge_model

    @staticmethod
    def MachiningToDict(hole: dict, is_back, hole_var):
        match_model = None
        # 垂直孔
        if int(hole['stat']) == 102:
            match_model = Algorithm.vertical(hole, is_back, hole_var)
        # 水平孔
        elif int(hole['stat']) == 103:
            match_model = Algorithm.horizontal(hole)
        # 铣型
        elif int(hole['stat']) == 105:
            match_model = Algorithm.groove(hole, is_back, hole_var)
        # 开槽
        elif int(hole['stat']) == 109:
            match_model = Algorithm.groove(hole, is_back, hole_var)
        elif int(hole['stat']) == 112:
            pass
        return match_model

    @staticmethod
    def MaterielToDict(materiel: dict):
        materiel_model = Metal()
        materiel_model.Id = random.randint(100, 100000)
        materiel_model.Name = materiel['Name']
        materiel_model.Num = materiel['Quantity']
        materiel_model.PartNumber = materiel['Spec']
        materiel_model.length = materiel['LENGTH']
        materiel_model.Unit = materiel['INFO3']
        return materiel_model

    @staticmethod
    def MPRInfoToMachining(mpr_info: dict, var: dict, is_back):
        for x in var.keys():
            for key, val in mpr_info.items():
                if x in str(val):
                    mpr_info[key] = eval(val.replace(x, var[x]))
        mpr_info['Height'] = var['_BSZ']
        return DataToModel.MachiningToDict(mpr_info, is_back, var)

    @staticmethod
    def shortContentToDict(mpr_info):
        mpr = {'stat': re.search(r'(\d+)', mpr_info).group(1)}
        if mpr['stat'] == "101":
            return
        element = mpr_info.split('\t')
        row_val = [x.replace(' ', '') for x in element if len(x.replace(' ', '')) > 0]
        for row in row_val:
            if str(row).count('=') == 1:
                el = re.search('(.*)?="(.*)?"', row)
                mpr[el.group(1)] = el.group(2)
        return mpr


if __name__ == '__main__':
    name = '20200708-01'
    dc = DataConvert('172.16.200.252', 'sa', '2020', 'LWJ')
    content = dc.getOrderInfo(name)
    # content = dc.readOneFileOrDir('F:\\铣型类型\\A123A1004.mpr')
    xml_content = xmltodict.unparse(content)
    FileExtend.writeFile('F:\\{}.xml'.format(name), xml_content)
    # MprConvert.readMprFile(r'\\172.16.200.251\product\CNC\AAAA\AAAA1027.mpr', 18, False)
    #
    # name = r'F:\铣型类型'
    # # name = 'A123A'
    # dc = DataConvert('172.16.200.252', 'sa', '2020', 'LWJ')
    # # content = dc.getOrderInfo(name)
    # if os.path.isdir(name):
    #     for file in os.listdir(name):
    #         content = dc.readOneFileOrDir('F:\\铣型类型\\{}.mpr'.format(file))
    #         xml_content = xmltodict.unparse(content)
    #         FileExtend.writeFile('F:\\测试mpr\\{}.xml'.format(file), xml_content)
    #         # MprConvert.readMprFile(r'\\172.16.200.251\product\CNC\AAAA\AAAA1027.mpr',
    # else:
    #     content = dc.readOneFileOrDir('F:\\铣型类型\\{}.mpr'.format(name))
    #     xml_content = xmltodict.unparse(content)
    #     FileExtend.writeFile('F:\\{}.xml'.format(name), xml_content)
    #     # MprConvert.readMprFile(r'\\172.16.200.251\product\CNC\AAAA\AAAA1027.mpr', 18, False)
